{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "fdeec28e-fe62-4b13-9220-06f21271f56d",
   "metadata": {},
   "source": [
    "# 自动微分"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "5cb0145e-0b79-4e45-a354-a42e07787791",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "9047de14-550a-4b49-b3f7-f646421c610b",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Variable:\n",
    "    def __init__(self, data):\n",
    "        # 检查data的数据类型是否为ndarray\n",
    "        if data is not None:\n",
    "            if not isinstance(data, np.ndarray):\n",
    "                raise TypeError('{} is not supported'.format(type(data)))\n",
    "                \n",
    "        # 实例的 data 属性是引用了传入的 data，而不是重新申请一个地址空间来存储传入的 data\n",
    "        self.data = data\n",
    "        self.grad = None\n",
    "        # 在调用类的对象时赋值\n",
    "        self.creator= None\n",
    "\n",
    "    def set_creator(self, func):\n",
    "        self.creator = func\n",
    "\n",
    "    # 递归\n",
    "    '''\n",
    "    def backward(self):\n",
    "        # 获取变量的创建函数\n",
    "        f = self.creator\n",
    "        # 如果创建函数是None说明Variable实例是非函数创建的反向传播结束\n",
    "        if f is not None:\n",
    "            # 获取创建函数的输入\n",
    "            x = f.input\n",
    "            # 调用函数反向传播计算导数\n",
    "            x.grad = f.backward(self.grad)\n",
    "            # 递归，调用创建函数的输入变量的backward方法\n",
    "            x.backward()\n",
    "    '''\n",
    "    # 循环\n",
    "    def backward(self):\n",
    "        # 如果self.grad为None,自动生成导数\n",
    "        if self.grad is None:\n",
    "            self.grad = np.ones_like(self.data)\n",
    "        # 列表funcs\n",
    "        funcs = [self.creator]\n",
    "        # 第一个Variable对象的creator属性为None\n",
    "        while funcs:\n",
    "            # 列表（list）的 pop 方法用于移除并返回列表中指定位置的元素\n",
    "            # 没有给 pop 方法传入参数，所以它会移除并返回列表 my_list 的最后一个元素\n",
    "            f = funcs.pop()\n",
    "            x, y = f.input, f.output\n",
    "            x.grad = f.backward(y.grad)\n",
    "\n",
    "            if x.creator is not None:\n",
    "                funcs.append(x.creator)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "c4323595-8a3f-4787-a2e3-50aab161ede9",
   "metadata": {},
   "outputs": [],
   "source": [
    "def as_array(x):\n",
    "    if np.isscalar(x):\n",
    "        return np.array(x)\n",
    "    return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "2c188768-3283-48bd-a3ef-cc2e70be7060",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Function:\n",
    "    '''\n",
    "    __call__():在调用类的对象时执行\n",
    "    input  : Variable class\n",
    "    output : Variable class\n",
    "    '''\n",
    "    def __call__(self, input):\n",
    "        # 定义x变量引用Variable类的实例对象input的数据data\n",
    "        x = input.data\n",
    "        # 调用Function类的对象时执行forward方法，定义x变量引用计算的函数值\n",
    "        y = self.forward(x)\n",
    "        # 创建Variable对象，把计算的结果保存在Variablel类的实例对象output中\n",
    "        output = Variable(as_array(y))\n",
    "        # 定义类本身self的属性input，用属性input于保存函数的输入\n",
    "        self.input = input\n",
    "        # 调用output变量引用的Variable对象的方法set_creator()，保存Function类的实例对象的引用\n",
    "        output.set_creator(self)\n",
    "        # 定义类本身self的属性output，用于保存函数的输出\n",
    "        self.output = output\n",
    "        return output\n",
    "\n",
    "    def forward(self, x):\n",
    "        raise NotImplementedError()\n",
    "\n",
    "    def backward(self, gy):\n",
    "        raise NotImplementedError()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "feb0990c-b0ef-4a16-bc2b-2fe33345d4ab",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Square(Function):\n",
    "    # 派生类中重新定义forward方法\n",
    "    def forward(self, x):\n",
    "        y = x**2\n",
    "        return y\n",
    "\n",
    "    # 派生类中重新定义backward方法\n",
    "    def backward(self, gy):\n",
    "        x = self.input.data\n",
    "        gx = 2 * x * gy\n",
    "        return gx"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "bdbd20fb-0762-47fa-9927-ae21bb3c0bc5",
   "metadata": {},
   "outputs": [],
   "source": [
    "class Exp(Function):\n",
    "    def forward(self, x):\n",
    "        y = np.exp(x)\n",
    "        return y\n",
    "\n",
    "    def backward(self, gy):\n",
    "        x = self.input.data\n",
    "        gx = np.exp(x) * gy\n",
    "        return gx"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "5c547a83-6055-4cca-8852-bace02881778",
   "metadata": {},
   "outputs": [],
   "source": [
    "def square(x):\n",
    "    return Square()(x)\n",
    "\n",
    "def exp(x):\n",
    "    return Exp()(x)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "91aae68d-9484-4342-98c6-0a6f6c3ad989",
   "metadata": {},
   "source": [
    "### 中心差分数值法：求函数$y = {(e^{x^2})}^2$的值；"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "e64db162-46e0-4f97-bc7a-462c59063e74",
   "metadata": {},
   "outputs": [
    {
     "ename": "TypeError",
     "evalue": "<class 'numpy.float64'> is not supported",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mTypeError\u001b[0m                                 Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[10], line 15\u001b[0m\n\u001b[0;32m     12\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m C(B(A(x)))\n\u001b[0;32m     14\u001b[0m x \u001b[38;5;241m=\u001b[39m Variable(np\u001b[38;5;241m.\u001b[39marray(\u001b[38;5;241m0.5\u001b[39m))\n\u001b[1;32m---> 15\u001b[0m dy \u001b[38;5;241m=\u001b[39m numerical_diff(f, x)\n\u001b[0;32m     16\u001b[0m \u001b[38;5;28mprint\u001b[39m(dy)\n",
      "Cell \u001b[1;32mIn[10], line 2\u001b[0m, in \u001b[0;36mnumerical_diff\u001b[1;34m(f, x, eps)\u001b[0m\n\u001b[0;32m      1\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mnumerical_diff\u001b[39m(f, x, eps\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1e-4\u001b[39m):\n\u001b[1;32m----> 2\u001b[0m     x0 \u001b[38;5;241m=\u001b[39m Variable(x\u001b[38;5;241m.\u001b[39mdata \u001b[38;5;241m-\u001b[39m eps)\n\u001b[0;32m      3\u001b[0m     x1 \u001b[38;5;241m=\u001b[39m Variable(x\u001b[38;5;241m.\u001b[39mdata \u001b[38;5;241m+\u001b[39m eps)\n\u001b[0;32m      4\u001b[0m     y0 \u001b[38;5;241m=\u001b[39m f(x0)\n",
      "Cell \u001b[1;32mIn[3], line 6\u001b[0m, in \u001b[0;36mVariable.__init__\u001b[1;34m(self, data)\u001b[0m\n\u001b[0;32m      4\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m data \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m      5\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(data, np\u001b[38;5;241m.\u001b[39mndarray):\n\u001b[1;32m----> 6\u001b[0m         \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mTypeError\u001b[39;00m(\u001b[38;5;124m'\u001b[39m\u001b[38;5;132;01m{}\u001b[39;00m\u001b[38;5;124m is not supported\u001b[39m\u001b[38;5;124m'\u001b[39m\u001b[38;5;241m.\u001b[39mformat(\u001b[38;5;28mtype\u001b[39m(data)))\n\u001b[0;32m      8\u001b[0m \u001b[38;5;66;03m# 实例的 data 属性是引用了传入的 data，而不是重新申请一个地址空间来存储传入的 data\u001b[39;00m\n\u001b[0;32m      9\u001b[0m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mdata \u001b[38;5;241m=\u001b[39m data\n",
      "\u001b[1;31mTypeError\u001b[0m: <class 'numpy.float64'> is not supported"
     ]
    }
   ],
   "source": [
    "def numerical_diff(f, x, eps=1e-4):\n",
    "    x0 = Variable(x.data - eps)\n",
    "    x1 = Variable(x.data + eps)\n",
    "    y0 = f(x0)\n",
    "    y1 = f(x1)\n",
    "    return (y1.data - y0.data) / (2 * eps)\n",
    "\n",
    "def f(x):\n",
    "    A = Square()\n",
    "    B = Exp()\n",
    "    C = Square()\n",
    "    return C(B(A(x)))\n",
    "\n",
    "x = Variable(np.array(0.5))\n",
    "dy = numerical_diff(f, x)\n",
    "print(dy)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b36eb78e-22c8-4697-af64-e2d29dabdcc8",
   "metadata": {},
   "source": [
    "### 反向传播法：求函数$y = {(e^{x^2})}^2$的值；"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "847f67cc-0600-4c19-946d-a0ff9c030535",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 先正向传播求数值：x-> A -> a -> B -> b -> C -> y\n",
    "# 创建Square,Exp和Square三个对象，分别用A,B,C变量来引用\n",
    "A = Square()\n",
    "B = Exp()\n",
    "C = Square()\n",
    "\n",
    "# y = C(B(A(x)))\n",
    "# 创建Variable对象，用变量x来引用\n",
    "x = Variable(np.array(0.5))\n",
    "# 调用A变量引用的Square对象计算\n",
    "a = A(x)\n",
    "print(\"a = \", a.data)\n",
    "b = B(a)\n",
    "print(\"b = \", b.data)\n",
    "y = C(b)\n",
    "print(\"y = \", y.data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "86d0b144-0800-4ded-ab82-b26c890798a1",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Check the backword connection: y -> C -> b -> B -> a -> A ->x\n",
    "assert y.creator == C\n",
    "assert y.creator.input == b\n",
    "assert y.creator.input.creator == B\n",
    "assert y.creator.input.creator.input == a\n",
    "assert y.creator.input.creator.input.creator == A\n",
    "assert y.creator.input.creator.input.creator.input == x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4c498ec7-fe9c-42d8-8e3f-2062f06a55a4",
   "metadata": {},
   "outputs": [],
   "source": [
    "# 手动反向传播求导：gy -> C -> B -> A -> gx\n",
    "y.grad = np.array(1.0)\n",
    "print(y.grad)\n",
    "b.grad = C.backward(y.grad)\n",
    "print(b.grad)\n",
    "a.grad = B.backward(b.grad)\n",
    "print(a.grad)\n",
    "x.grad = A.backward(a.grad)\n",
    "print(x.grad)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "3c8df1ef-c300-4647-b3ef-ce058ea15b5c",
   "metadata": {},
   "outputs": [],
   "source": [
    "A = Square()\n",
    "B = Exp()\n",
    "C = Square()\n",
    "\n",
    "# 正向传播计算，并建立正反向的连接：x <-> A <-> a <-> B <-> b <-> C <-> y\n",
    "x = Variable(np.array(0.5))\n",
    "#a = A(x)\n",
    "#b = B(a)\n",
    "#y = C(b)\n",
    "y = square(exp(square(x)))\n",
    "\n",
    "#y.grad = np.array(1.0) \n",
    "# 利用反向连接，自动反向传播计算求导\n",
    "y.backward()\n",
    "print(x.grad)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python [conda env:base] *",
   "language": "python",
   "name": "conda-base-py"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.12.7"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
